#ifndef canvasgl_H
#define canvasgl_H

#include <QRectF>
#include <QPixmap>
#include <QWidget>
#include <QPointF>
#include <QTextOption>
#include <QTransform>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QtOpenGL>
#include <QGLWidget>

class QPainter;
class QSize;
class QGLPixelBuffer;

//////////////////////////////////////////////////////////////////////////
class canvasgl : public QGLWidget
{
  Q_OBJECT

public:
    canvasgl(QWidget *parent, int y_dir_ = UPWARD_Y);
    ~canvasgl();

    QSize minimumSizeHint() const;

    QSize sizeHint() const;

    enum { Margin = 50 };

    double margin_draw;

    // key pressed
    bool shift_key;
    bool control_key;

    /**
    * Field constant used to define the vertical Y axis of the canvas in 
    * downward direction.
    */
    static const int DOWNWARD_Y = 0;

    /**
    * Field constant used to define the vertical Y axis of the canvas in 
    * upward direction.
    */
    static const int UPWARD_Y = 1;

    /**
    * Field constant used to define transparent text background opacity.
    */
    static const int TRANSPARENT_ = 0;

    /**
    * Field constant used to define opaque text background opacity.
    */
    static const int OPAQUE_ = 1;

    /**
    * Redisplay abstract method.  Client's concrete class must implement it.
    * This method is called every time the canvas is updated.
    * @param g is the 2D graphics context that the client uses to display
    */
    virtual void redraw( QPainter *g ) = 0;

    virtual void findMaxMin (double *xmin, double *xmax, double *ymin, double *ymax) = 0;

    /**
    * Mouse button pressed abstract method.  Client's concrete class must 
    * implement it.  This method is called when the user presses a mouse 
    * button in the canvas.
    * @param e is mouse event object
    * @param pt is the mouse position in client's floating point world coordinates
    */
    virtual void mousePressedWC( QMouseEvent *e, QPointF *pt ) = 0;

    /**
    * Mouse button dragged abstract method.  Client's concrete class must 
    * implement it.  This method is called when user drags the mouse in the 
    * canvas with a button pressed.
    * @param e is mouse event object
    * @param pt is the mouse position in client's floating point world coordinates
    */
    virtual void mouseDraggedWC ( QMouseEvent *e, QPointF *pt ) = 0;

    /**
    * Mouse button released abstract method.  Client's concrete class must 
    * implement it.  This method is called when user the mouse button at the
    * end of a mouse pressed-dragged-released interaction.
    * @param e is mouse event object
    * @param pt is the mouse position in client's floating point world coordinates
    */
    virtual void mouseReleasedWC( QMouseEvent *e, QPointF *pt ) = 0;


    /**
    * Sets the limits of the world coordinate window for the target canvas.
    * The limits are adjusted to be consistent with the canvas
    * viewport aspect ratio and with the canvas vertical distortion 
    * factor.  The adjustment is such that the center of the given
    * window is maintained.
    * <p>
    * The canvas is not automatically repainted by this method.
    * @param xmin is the left limit of target window
    * @param xmax is the right limit of target window
    * @param ymin is the bottom limit of target window if vertical Y axis is upward, otherwise it is the top limit
    * @param ymax is the top limit of target window if vertical Y axis is upward, otherwise it is the bottom limit
    * @see #world_area
    * @see #setVDistortion
    */
    void setWorld( double xmin, double xmax, double ymin, double ymax );
    

    /**
    * Translates the world coordinate window of the target canvas in the 
    * horizontal direction based on a given pan factor.
    * This factor is a percentage of the current window horizontal 
    * size.  A negative factor indicates a window translation to 
    * the right (the client model will move left), and a positive 
    * factor indicates a window translation to the left (the client
    * model will move right).
    * <p>
    * The canvas is not automatically repainted by this method.
    * @param fac is the pan factor
    * @see #world_area
    * @see #setWorld
    */
    void panWorldX( double fac );

    /**
    * Translates the world coordinate window of the target canvas in the 
    * vertical direction based on a given pan factor.
    * This factor is a percentage of the current window horizontal 
    * size. If the vertical Y axis is upward, a negative factor indicates
    * a upward window translation (the client model will move downward), 
    * and a positive factor indicates a downward window translation (the 
    * client model will move upward).  If the vertical Y axis is downward,
    * the reverse will occur.
    * <p>
    * The canvas is not automatically repainted by this method.
    * @param fac is the pan factor
    * @see #world_area
    * @see #setWorld
    */
    void panWorldY( double fac );

    /**
    * Scales the world coordinate window of the target canvas based on the 
    * given scaling factor. This factor is a percentage of decrease 
    * (positive factor) or increase (negative factor) of the window sizes.
    * The result of this inversion (positive ==> decrease, negative ==>
    * increase) is that the size of the client's enviroment 
    * decreases with a negative factor and increases with a positive
    * factor.
    * <p>
    * The canvas is not automatically repainted by this method.
    * @param fac is the scaling factor
    * @see #world_area
    * @see #setWorld
    */
    void scaleWorld( double fac );

    /**
    * Sets the WC window vertical distortion factor.  This factor specifies
    * the amount of distortion between the height and width of the world
    * coordinate area. It is essentially a factor between the vertical and
    * horizontal scales in client world coordinate space. A factor with 
    * value greater than one will cause a vertical stretching of the 
    * image.
    * <p>
    * The canvas is not automatically repainted by this method.
    * @param vdistortion is the vertical distortion factor
    * @see #vdistortion
    * @see #setWorld
    */
    void setVDistortion( double vdistortion );

    /**
    * Gets the WC window vertical distortion factor.
    * @return vertical distortion factor
    * @see #vdistortion
    * @see #setVDistortion
    */
    double getVDistortion();


    /**
    * Transforms the coordinates of a point given in client space (WC) to
    * device-dependent (raster) coordinates. The canvas device space has 
    * its origin in the upper-left corner of the canvas.
    * The <code>canvasgl</code> object contains an {@link AffineTransform} 
    * object that defines how to convert coordinates from client space (world 
    * coordinates) to device-dependent (raster) coordinates in device space.
    * Note that, to avoid creating new point objects, the method returns a 
    * reference to a private object and not a copy of a new point.  If 
    * needed, clients must clone the returned object.
    * @param world_x is the x coordinate of given point in client space
    * @param world_y is the y coordinate of given point in client space
    * @return point in raster (device) coordinates
    * @see #setWorld
    * @see #raster2world
    */
    void world2raster( double world_x, double world_y, QPoint *pt_raster);

    /**
    * Transforms the coordinates of a point given in device space (raster) to
    * client space (world coordinates). The canvas device space has 
    * its origin in the upper-left corner of the canvas.
    * The <code>canvasgl</code> object contains an {@link AffineTransform} 
    * object that defines how to convert coordinates from device-dependent 
    * (raster) coordinates in device space to client space (world 
    * coordinates).
    * Note that, to avoid creating new point objects, the method returns a 
    * reference to a private object and not a copy of a new point.  If 
    * needed, clients must clone the returned object.
    * @param raster_x is the x coordinate of the given point in device space
    * @param raster_y is the y coordinate of the given point in device space
    * @return point in world (client) coordinates
    * @see #setWorld
    * @see #world2raster
    */
    void raster2world( int raster_x, int raster_y, QPointF *pt_raster );


    /**
    * Update method of canvas.
    * <br>
    * In case the sizes of the canvas have changed, it (re)builds a 
    * {@link BufferedImage} object (offscreen image) to implement
    * double buffering rendering.  In this case, the world coordinate window 
    * defined by the client (if defined) is adjusted according to the new canvas
    * sizes.
    * <br>
    * Then, it clears the canvas using the currently defined background color and
    * calls the client's redraw method, passing the {@link Graphics2D} graphics 
    * context object of the offscreen image, so that the client may display
    * on it.
    * <br>
    * Finally, it fills the canvas with the image that was displayed by the canvas
    * on the offscreen image.	 
    * @param g is the target graphics context
    * @see #setWorld
    * @see #redraw
    */
    void refreshPixmap( );

    void fit ();
    void getWindow (double *xmin, double *xmax, double *ymin, double *ymax);

    double getTol ( ) { return toler;}

protected:

  void mousePressEvent   (QMouseEvent *event);
  void mouseMoveEvent    (QMouseEvent *event);
  void mouseReleaseEvent (QMouseEvent *event);
  void wheelEvent        (QWheelEvent *event);

  void keyPressEvent     (QKeyEvent * event);
  void keyReleaseEvent   (QKeyEvent * event);

  // method to necessary to QT-opengl
  void initializeGL();
  void resizeGL(int width, int height);
  void paintGL();


private:

  /**
  * Keeps the vertical Y axis direction state.  The possible values are 
  * <code>DOWNWARD_Y</code> (default) or <code>UPWARD_Y</code>.  The 
  * Y axis direction is specified during creation, when the
  * <code>canvasgl</code> constructor is called.
  */
  int y_dir;


private:

  /**
  * Defines the canvas raster area. This area corresponds to the sizes of the 
  * canvas in pixels.
  */
  QSize *raster_area;

  /**
  * Defines the canvas world coordinate area. This area corresponds to the 
  * window in client space which is visible in the canvas.  This window is
  * adjusted to have the same aspect ratio between height and width of the
  * canvas (in its current state).  When the client specifies the windows,
  * the adjustment maintains the center of given (raw) window in the same 
  * position.  This adjustment may be affected by the canvas vertical 
  * distortion factor. 
  */
  QRectF *world_area;

  /**
  * Defines the original canvas world coordinate area. This area corresponds 
  * to the window in world coordinates which was set by the client.  That is,
  * it corresponds to the world area prior to adjustment with respect to the 
  * canvas aspect ratio between height and width. 
  */
  QRectF  *world_raw;

  /**
  * BufferedImage object that is used to implement double
  * buffering rendering.  This object defines a Graphics2D 
  * graphics context object used to convert from world coordinates 
  * to raster coordinates.
  */
  // QPixmap pixmap;
  QGLPixelBuffer *pbuffer;

  /**
  * Graphics context object (Graphics2D) that contains an 
  * AffineTransform object that defines how to convert 
  * coordinates from client space (world coordinates) to device-dependent 
  * (raster) coordinates in device space.
  */
  QPainter *g2off;


  /**
  * Keeps the WC window vertical distortion factor.  This factor specifies
  * the amount of distortion between the height and width of the world
  * coordinate area. It is essentially a factor between the vertical and
  * horizontal scales in client world coordinate space. A factor with 
  * value greater than one will cause a vertical stretching of the 
  * image.
  */
  double vdistortion;

  /**
  * Private field that holds a auxiliary point in world coordinates.
  */
  QPointF pt_world;

  /**
  * Private field that holds a auxiliary point in raster (device) coordinates.
  */
  QPoint pt_raster;

  /**
  * Private field that holds a mouse event point in world coordinates.
  */
  QPointF ept_world;

  /**
  * Private field that holds a mouse event point in raster (device) coordinates.
  */
  QPoint ept_raster;

  /**
  * Private field that holds current text alignment parameter.
  */
  int text_opacity;

  /**
  * Private field that holds text box for opaque displaying.
  */
  QRect text_box;

  /**
  * Private field that holds current text alignment parameter.
  */
  QTextOption text_alignment;

  /**
  * Private field that holds current font that is used to display text.
  */
  QFont fnt;

  /**
  * Font renderer context: private field that is a container for 
  * the information needed to measure text that is displayed.
  */
  // private FontRenderContext frc;


  /**
  * Private field that holds an auxiliary identity affine 
  * transformation matrix.
  */
  QTransform identity;

  /**
  * Internal tolerance based on window word
  */
  double toler;



  /**
  * Creates the BufferedImage object that is used to implement double
  * buffering rendering.  This object defines a Graphics2D graphics 
  * context object used to convert from world coordinates 
  * to raster coordinates.
  */
  void buildOffscreen();

  /**
  * Adjusts the window in world coordinates (world_area) to have the 
  * same aspect ratio between height and width of the canvas (in its 
  * current state).
  * The adjustment maintains the center of given (raw) window in the same 
  * position.  This adjustment may be affected by the canvas vertical 
  * distortion factor (vdistortion).
  */
  void adjustWorld();


  //
  void canvasToRaster( int *xr, int *yr );


};

#endif // canvasgl_H
